Aprendizaje No Supervisado. La Maratón de Tokyo 2025.

Autores/as

Alba Martínez de la Hermosa

Alonso González Romero

Daniel López Paredes

Fecha de publicación

16 de noviembre de 2025

Resumen
Por realizar
Palabras clave

maratón, análisis de datos, Tokyo

1 Importacion de librerías y datos

# Importo el df de resultadosTokyo tras el EDA realizado:
resultadosTokyo2025 <- readRDS("resultadosTokyo2025.rds")

# Importo las librerías necesarias:
source("librerias.R")
library(caret)
library(rsample)

2 Análisis de Componentes Principales

El Análisis de Componentes Principales (PCA, por sus siglas en inglés Principal Component Analysis) es una técnica de reducción de dimensionalidad no supervisada que transforma un conjunto de variables correlacionadas en un conjunto más pequeño de variables no correlacionadas llamadas componentes principales.

2.1 PCA sobre ritmos de carrera a través de los parciales

En esta sección se realiza un Análisis de Componentes Principales (PCA) para la categoría Élite. Se utilizan los tiempos reales de cada segmento de 5km junto con el segmento de 40km a meta, calculados a partir de los tiempos acumulados proporcionados en los parciales. A través de ellos, se dividirá cada parcial por el tiempo final del corredor con el objetivo de captar el porcentaje de tiempo que dedica a cada segmento durante la carrera. Por último y antes de iniciar el PCA, estandarizamos los datos para asegurar que los parciales de 5km y el de 2.195 km tengan la misma importancia en el análisis.

2.1.1 Preparación de datos

# Filtrar por categoría Élite
elite <- resultadosTokyo2025 %>%
  filter(categoria == "Élite")

# Calcular parciales reales (tiempos de cada segmento)
elite <- elite %>%
  mutate(
    seg_0_5 = parcial_5km,
    seg_5_10 = parcial_10km - parcial_5km,
    seg_10_15 = parcial_15km - parcial_10km,
    seg_15_20 = parcial_20km - parcial_15km,
    seg_20_25 = parcial_25km - parcial_20km,
    seg_25_30 = parcial_30km - parcial_25km,
    seg_30_35 = parcial_35km - parcial_30km,
    seg_35_40 = parcial_40km - parcial_35km,
    seg_40_meta = tiempo_oficial - parcial_40km
  )

# Normalizar por tiempo oficial (porcentaje de tiempo en cada segmento)
elite <- elite %>%
  mutate(
    rel_seg_0_5 = seg_0_5 / tiempo_oficial,
    rel_seg_5_10 = seg_5_10 / tiempo_oficial,
    rel_seg_10_15 = seg_10_15 / tiempo_oficial,
    rel_seg_15_20 = seg_15_20 / tiempo_oficial,
    rel_seg_20_25 = seg_20_25 / tiempo_oficial,
    rel_seg_25_30 = seg_25_30 / tiempo_oficial,
    rel_seg_30_35 = seg_30_35 / tiempo_oficial,
    rel_seg_35_40 = seg_35_40 / tiempo_oficial,
    rel_seg_40_meta = seg_40_meta / tiempo_oficial
  )

# Seleccionar solo las variables para PCA
elite_pca_data <- elite %>%
  select(Nombre, Genero, estrategia, starts_with("rel_seg_")) %>%
  na.omit()

cat("✓ Datos preparados para categoría Élite\n")
✓ Datos preparados para categoría Élite
cat("✓ Número de corredores:", nrow(elite_pca_data), "\n")
✓ Número de corredores: 92 
cat("✓ Variables:", ncol(elite_pca_data) - 3, "segmentos normalizados\n")
✓ Variables: 9 segmentos normalizados

2.1.2 Análisis de Componentes Principales

# Realizar PCA (sin incluir las columnas Nombre, Genero, Estrategia)
pca_elite <- prcomp(elite_pca_data[, -c(1:3)], center = TRUE, scale. = TRUE)

# Resumen
cat("\n=== RESUMEN PCA - ÉLITE ===\n")

=== RESUMEN PCA - ÉLITE ===
print(summary(pca_elite))
Importance of components:
                          PC1    PC2     PC3     PC4     PC5     PC6     PC7
Standard deviation     2.2191 1.5855 0.84810 0.61372 0.44565 0.33824 0.31198
Proportion of Variance 0.5472 0.2793 0.07992 0.04185 0.02207 0.01271 0.01081
Cumulative Proportion  0.5472 0.8265 0.90642 0.94827 0.97034 0.98305 0.99386
                           PC8       PC9
Standard deviation     0.23502 1.921e-15
Proportion of Variance 0.00614 0.000e+00
Cumulative Proportion  1.00000 1.000e+00
# Autovalores
autovalores_elite <- pca_elite$sdev^2
cat("\n=== AUTOVALORES ===\n")

=== AUTOVALORES ===
print(autovalores_elite)
[1] 4.924559e+00 2.513934e+00 7.192802e-01 3.766523e-01 1.986049e-01
[6] 1.144044e-01 9.733314e-02 5.523221e-02 3.690119e-30
# Varianza explicada
var_explicada <- round(autovalores_elite / sum(autovalores_elite) * 100, 2)
cat("\n=== VARIANZA EXPLICADA POR COMPONENTE (%) ===\n")

=== VARIANZA EXPLICADA POR COMPONENTE (%) ===
print(var_explicada)
[1] 54.72 27.93  7.99  4.19  2.21  1.27  1.08  0.61  0.00
# Varianza acumulada
var_acum_elite <- cumsum(var_explicada)
cat("\n=== VARIANZA ACUMULADA (%) ===\n")

=== VARIANZA ACUMULADA (%) ===
print(var_acum_elite)
[1]  54.72  82.65  90.64  94.83  97.04  98.31  99.39 100.00 100.00

Observamos que, con las 2 primeras componentes principales, se explica un 82.65% de la varianza total, lo que indica que estas dos componentes capturan la mayor parte de la información contenida en los datos originales.

2.1.3 Autovectores (Loadings)

A continuación, visualizamos los autovectores (loadings) correspondientes a las dos primeras componentes principales:

cat("=== AUTOVECTORES - PC1 y PC2 ===\n")
=== AUTOVECTORES - PC1 y PC2 ===
print(pca_elite$rotation[, 1:2])
                       PC1        PC2
rel_seg_0_5     -0.3735426 -0.1912469
rel_seg_5_10    -0.3997243 -0.2086332
rel_seg_10_15   -0.4248876 -0.1053469
rel_seg_15_20   -0.3963012  0.1357340
rel_seg_20_25   -0.1636699  0.5122632
rel_seg_25_30    0.1295767  0.5315866
rel_seg_30_35    0.3009107  0.3113988
rel_seg_35_40    0.3879472 -0.2821189
rel_seg_40_meta  0.2801323 -0.4108733

Observamos que la primera componente principal (PC1) contiene una especie de media ponderada en la cual, se dan valores negativos de manera creciente a medida que avanzan los segmentos de la carrera. A partir de los 20km el valor absoluto de la PC1 se reduce de forma notable y vuelve a incrementar en los tramos de los 30km y los 35km. Un mayor valor absoluto en los parciales hasta los 20km y en los tramos 30-35km y 35-40km sugiere que para esta primera componente principal esos tramos son los más influyentes.

En cuanto a los signos, son todos negativos hasta los 25km, momento a partir del cual los siguientes segmentos comienzan a tomar valores positivos de manera incremental, bajando un poco el valor del segmento desde los 40km a meta. Esto significa que si se proyecta la PC1 sobre un atleta y este tiene un mayor porcentaje de tiempo normalizado en el primer tramo de la carrera, su PC1 tenderá a bajar. Si por el contrario, tiene un mayor porcentaje de tiempo normalizado en el último tramo, con valor positivo, su PC1 tenderá a subir.

A partir de aquí se deduce que los atletas que tengan un PC1 más bajo habrán pasado más porcentaje de tiempo relativo en los tramos iniciales que en los finales en comparación con el promedio (comienzo más lento y final más rápido). Los que tengan un PC1 más alto habrán pasado menos porcentaje de tiempo relativo en los tramos iniciales que en los finales en comparación con el promedio (comienzo más rápido y final más lento).

Para facilitar la comprensión del análisis, esta primera componente puede recibir el nombre de comienzo-fin de la carrera.

Importante

No se está haciendo referencia a tiempos ni ritmos absolutos, sino a la distribución del porcentaje de tiempo de las carreras individuales. Un atleta puede haber emepezado más rápido en términos absolutos pero tener un valor de PC1 más bajo porque, por su propia distribución del ritmo a lo largo de la carrera, ha pasado un porcentaje de tiempo mayor en el primer tramo. De igual forma, un atleta puede haber pasado un porcentaje de tiempo menor en el primer tramo de la carrera (en comparación con el resto de porcentajes de su carrera), y por ello tener un valor de PC1 más alto, pero su ritmo en términos absolutos puede ser más lento que el de otros corredores.

Respecto a la segunda componente principal (PC2), se observan valores ligeramente negativos en los 3 primeros segmentos de la carrera, seguido de valores altamente positivos en los parciales de 20 a 30km y valores negativos en los dos últimos segmentos.

Los valores altos en los segmentos intermedios indican que estos son los tramos que más influyen en la PC2. Los símbolos negativo y positivo reflejan que los tramos iniciales (0-5, 5-10 y 10-15) y finales (35-40 y 40-meta) tienden a bajar el valor de la PC2 mientras que los tramos intermedios (15-20, 20-25, 25-30, 30-35) tienden a subirlo.

Al igual que se hizo con la PC1, si se proyectara la PC2 sobre un atleta y este hubiera pasado un porcentaje de tiempo relativo mayor en los tramos intermedios, tendría un valor de PC2 más alto que si hubiera pasado un porcentaje de tiempo relativo mayor en los tramos iniciales y finales.

La deducción que se obtiene de esta proyección es que los atletas que tienen un valor de PC2 alto han empleado un porcentaje de tiempo relativo mayor en los tramos intermedios en comparación con el promedio (ritmo central más lento), mientras que los que tienen un valor de PC2 más bajo han empleado un porcentaje de tiempo relativo menor (ritmo central más rápido).

Esta segunda componente puede recibir el nombre de centro de carrera.

Importante

Los tramos centrales que más importancia tienen en la PC2 (de los 20km a los 35km, con énfasis entre los 20km y 30km) corresponden con el “muro”, momento en el que el rendimiento disminuye muy notablemente. De esta forma, la PC2 podría reflejar una bajada de rendimiento excesiva si se ven valores muy altos. Para comprobarlo se puede acudir a elite_pca_data y observar el porcentaje de tiempo normalizado en esos tramos comparados con el resto de porcentajes de la carrera.

2.1.4 Gráfica de PCA - Élite

# Preparar datos para graficar
scores_elite <- as.data.frame(pca_elite$x[, 1:2])
scores_elite$Nombre <- elite_pca_data$Nombre
scores_elite$Genero <- elite_pca_data$Genero
scores_elite$estrategia <- elite_pca_data$estrategia

# Mostrar algunos valores
cat("\n=== PRIMEROS 10 CORREDORES EN ESPACIO PC1-PC2 ===\n")

=== PRIMEROS 10 CORREDORES EN ESPACIO PC1-PC2 ===
print(head(scores_elite, 10))
           PC1         PC2                   Nombre Genero estrategia
1  -3.00929499  0.27091310            TADESE TAKELE    Men   Uniforme
2  -2.40197373 -0.27433337            DERESA GELETA    Men   Uniforme
3  -2.17541835 -0.45609304 VINCENT KIPKEMOI NGETICH    Men   Uniforme
4  -0.09199066 -0.10165290            TITUS KIPRUTO    Men   Positiva
5  -1.94426317 -0.36048314       MULUGETA ASEFA UMA    Men   Uniforme
6  -3.38502096  0.26856368       GEOFFREY TOROITICH    Men   Uniforme
7   0.20325598 -0.16819457           BENSON KIPRUTO    Men   Positiva
8  -3.21935698 -0.04320958            SULDAN HASSAN    Men   Uniforme
9  -1.71207172 -0.68418024         JOSHUA CHEPTEGEI    Men   Uniforme
10 -3.11607220 -0.08603310         TSUBASA ICHIYAMA    Men   Uniforme

En esta lista se puede observar cómo los atletas que tienen valores más bajos en la PC1 (a partir de -1.7) son los que siguen una estrategia uniforme mientras que los que tienen valores más altos (cercanos a 0) siguen una estrategia positiva.

En cuanto a la PC2, todos los valores son bajos, muy cercanos a 0, indicando que:

  1. Para los que han llevado una estrategia uniforme, la distribución relativa de los porcentajes de tiempo no ha sufrido grandes variaciones en ningún momento de la carrera con respecto al promedio promedio.

  2. Para los deportistas que han llevado una estrategia positiva, la caída en el rendimiento se ha dado más cerca de los kilómetros finales (a partir de los 35) que de los intermedios, es decir, los tramos intermedios de la carrera no han sido especialmente influyentes en comparación con el promedio. Esto es coherente con sus valores de PC1 (0 comparado a los valores a partir de -1.7 de los otros atletas), pues los valores altos en PC1 reflejaban un comienzo más rápido y un final más ralentizado (menor porcentaje de tiempo relativo al principio en contraposición con el final).

Importante

No todas las estrategias presentarán los mismos valores de PC1 y PC2. Por ejemplo, un corredor puede llevar una estrategia positiva por haber disminuido su rendimiento en los tramos finales de la carrera o por haberlo disminuido en los tramos intermedios hasta la meta. En el primero de los casos, la PC2, al referirse a los kilómetros centrales, no recogería la pérdida de velocidad relativa. En el segundo, dicha pérdida se vería reflejada tanto en la PC1 y como en la PC2. La consistencia de la interpretación radica en la coherencia de cada estrategia con el significado de las componentes principales.

2.1.4.1 Gráfico estático con ggplot2

# Gráfica estática básica
ggplot(
  scores_elite,
  aes(x = PC1, y = PC2, color = Genero, shape = estrategia)
) +
  geom_point(size = 4, alpha = 0.7) +
  labs(
    title = "PCA - Categoría Élite (por Género y Estrategia)",
    x = paste0("PC1 (", var_explicada[1], "%)"),
    y = paste0("PC2 (", var_explicada[2], "%)"),
    color = "Género",
    shape = "Estrategia"
  ) +
  scale_color_manual(values = c("Men" = "#1976D2", "Women" = "#E91E63")) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold", size = 14, hjust = 0.5),
    panel.grid.major = element_line(color = "gray90"),
    panel.grid.minor = element_blank(),
    legend.position = "right"
  )

2.1.4.2 Gráfico interactivo con plotly

library(plotly)

# Crear variable combinada para colores únicos por género y estrategia
scores_elite <- scores_elite %>%
  mutate(grupo = paste0(Genero, " - ", estrategia))

# Definir paleta de colores combinada (género como tono base, estrategia como variación)
colores_combinados <- c(
  # Hombres (tonos azules)
  "Men - Negativa" = "#1E3A8A",
  "Men - Positiva" = "#2563EB",
  "Men - Uniforme" = "#60A5FA",
  "Men - Variable" = "#93C5FD",
  # Mujeres (tonos rosas/morados)
  "Women - Negativa" = "#831843",
  "Women - Positiva" = "#BE185D",
  "Women - Uniforme" = "#EC4899",
  "Women - Variable" = "#F9A8D4"
)

# Calcular rangos fijos de los ejes
rango_x <- range(scores_elite$PC1, na.rm = TRUE)
rango_y <- range(scores_elite$PC2, na.rm = TRUE)
margen_x <- diff(rango_x) * 0.1
margen_y <- diff(rango_y) * 0.1

# Crear figura base
fig <- plot_ly()

# Agregar trazas por cada combinación de género y estrategia
for (genero in unique(scores_elite$Genero)) {
  for (estrat in unique(scores_elite$estrategia)) {
    datos_grupo <- scores_elite %>%
      filter(Genero == genero, estrategia == estrat)

    if (nrow(datos_grupo) > 0) {
      fig <- fig %>%
        add_trace(
          data = datos_grupo,
          x = ~PC1,
          y = ~PC2,
          type = "scatter",
          mode = "markers",
          name = paste0(genero, " - ", estrat),
          legendgroup = genero,
          legendgrouptitle = list(text = genero),
          marker = list(
            size = 12,
            color = colores_combinados[paste0(genero, " - ", estrat)],
            opacity = 0.85,
            line = list(
              color = "white",
              width = 1.5
            )
          ),
          text = ~ paste0(
            "<b style='font-size:14px'>",
            Nombre,
            "</b><br>",
            "<br>",
            "<b>Género:</b> ",
            Genero,
            "<br>",
            "<b>Estrategia:</b> ",
            estrat,
            "<br>",
            "<br>",
            "<b>PC1:</b> ",
            round(PC1, 4),
            "<br>",
            "<b>PC2:</b> ",
            round(PC2, 4)
          ),
          hovertemplate = "%{text}<extra></extra>"
        )
    }
  }
}

# Configurar layout con ejes fijos
fig <- fig %>%
  layout(
    title = list(
      text = "<b>Análisis de Componentes Principales - Categoría Élite</b><br><sub>Estrategias de carrera en el Maratón de Tokio 2025</sub>",
      font = list(
        size = 18,
        family = "Arial, sans-serif",
        color = "#2C3E50"
      ),
      x = 0.5,
      xanchor = "center"
    ),
    xaxis = list(
      title = list(
        text = paste0("<b>PC1 (", var_explicada[1], "%)</b>"),
        font = list(size = 14, family = "Arial", color = "#34495E")
      ),
      range = c(rango_x[1] - margen_x, rango_x[2] + margen_x),
      fixedrange = FALSE,
      gridcolor = "#E8E8E8",
      gridwidth = 1,
      showgrid = TRUE,
      zeroline = TRUE,
      zerolinecolor = "#95A5A6",
      zerolinewidth = 2,
      tickfont = list(size = 11, color = "#34495E")
    ),
    yaxis = list(
      title = list(
        text = paste0("<b>PC2 (", var_explicada[2], "%)</b>"),
        font = list(size = 14, family = "Arial", color = "#34495E")
      ),
      range = c(rango_y[1] - margen_y, rango_y[2] + margen_y),
      fixedrange = FALSE,
      gridcolor = "#E8E8E8",
      gridwidth = 1,
      showgrid = TRUE,
      zeroline = TRUE,
      zerolinecolor = "#95A5A6",
      zerolinewidth = 2,
      tickfont = list(size = 11, color = "#34495E")
    ),
    hovermode = "closest",
    hoverlabel = list(
      bgcolor = "white",
      font = list(
        family = "Arial, sans-serif",
        size = 13,
        color = "#2C3E50"
      ),
      bordercolor = "#BDC3C7",
      align = "left"
    ),
    legend = list(
      title = list(
        text = "<b>Género y Estrategia</b>",
        font = list(size = 13, family = "Arial")
      ),
      font = list(size = 11, family = "Arial"),
      orientation = "v",
      x = 1.02,
      y = 1,
      bgcolor = "rgba(255, 255, 255, 0.95)",
      bordercolor = "#BDC3C7",
      borderwidth = 1,
      tracegroupgap = 10
    ),
    plot_bgcolor = "#FAFAFA",
    paper_bgcolor = "#FFFFFF",
    margin = list(l = 80, r = 180, t = 100, b = 80)
  ) %>%
  config(
    displayModeBar = TRUE,
    displaylogo = FALSE,
    modeBarButtonsToRemove = c("lasso2d", "select2d"),
    toImageButtonOptions = list(
      format = "png",
      filename = "pca_elite_tokyo2025",
      height = 800,
      width = 1200,
      scale = 2
    )
  )

# Mostrar gráfico
fig

En estas gráficas se representa lo que se exponía con anterioridad dibujando cada atleta en dos dimensiones. El atleta es cada uno de los puntos de la gráfica. El color distingue su género, y la forma del punto (en la primera gráfica) o la intensidad del color (en la segunda gráfica) indican la estrategia que ha llevado a cabo. Las dos dimensiones son las dos componentes principales.

La mayoría de los puntos que están a la derecha del 0 en la PC1 son los deportistas que llevaron una estrategia positiva, independitentemente de los valores de la PC2. Los que corrieron de forma uniforme se sitúan a la izquierda del 0 en la PC1 y cerca del 0 en la PC2. Además, los que tienen valores más bajos en la PC1 son también los que más cerca se situán del valor 0 de la PC2.

Resulta llamativo que la mayoría de mujeres tuvieron menos variabilidad en el ritmo relativo que los hombres, hecho que también se observó durante el EDA al comparar las estrategias. Podría deberse a la mayor proporción de fibras tipo I (las más resistentes) del organismo femenino con respecto al masculino (citar).

2.1.5 Respondiendo preguntas

Con este PCA se puede responder a la pregunta formulada al final del EDA “¿En qué momento o momentos hay un cambio notable en el ritmo de los atletas?”. Se ha visto que en función de los valores en PC1 y PC2 se puede estimar la zona de la carrera en la que el cambio en el rendimiento, si lo hubo, fue remarcable. En los 10 atletas de élite que se han tomado de ejemplo antes de elaborar las gráficas, solo 2 aumentaron su porcentaje de tiempo relativo a partir de un determinado tramo. En estos casos fue a partir del kilómetro 35. Esta variación relativa refleja un cambio de ritmo significativo a falta de 7km y 195m para terminar la carrera.

3 Clustering

¿Sería interesante? Para diferenciar dentro de la élite los grupos que hay en función de las estrategias. Ver correlaciones entre el número de ganadores o TOP3 en cada grupo estratégico. Usar estas correlaciones y papers para respaldar con metodos estadísticos la evidencia de que estrategia uniforme es mejor para la maraton.

4 Modelo de regresión lineal para predecir el tiempo de la maratón

En vez de usar todo esto, usar las dimensiones del PCA para predecir el tiempo de la maratón? O se pueden hacer las dos cosas?

  1. con los parciales hasta la media maratón
  2. con los tiempos desde la media maratón
  3. con los parciales hasta la media maratón teniendo en cuenta la edad
  4. con los parciales desde la media maratón teniendo en cuenta la edad
  5. teniéndolo todo en cuenta
names(resultadosTokyo2025)
 [1] "BIB"                "Nombre"             "Nacionalidad"      
 [4] "Genero"             "Edad"               "tiempo_oficial"    
 [7] "parcial_5km"        "parcial_10km"       "parcial_15km"      
[10] "parcial_20km"       "medio_maraton"      "parcial_25km"      
[13] "parcial_30km"       "parcial_35km"       "parcial_40km"      
[16] "Pais_Estandarizado" "CODE"               "ritmo_oficial"     
[19] "ritmo_5km"          "ritmo_10km"         "ritmo_15km"        
[22] "ritmo_20km"         "ritmo_25km"         "ritmo_30km"        
[25] "ritmo_35km"         "ritmo_40km"         "categoria"         
[28] "grupo_edad"         "estrategia"         "TPI"               
modelo_parcial_hasta_mm <- lm(tiempo_oficial ~ parcial_5km + parcial_10km + parcial_15km + parcial_20km, medio_maraton,
               data = resultadosTokyo2025)

summary(modelo_parcial_hasta_mm)

Call:
lm(formula = tiempo_oficial ~ parcial_5km + parcial_10km + parcial_15km + 
    parcial_20km, data = resultadosTokyo2025, subset = medio_maraton)

Residuals:
     Min       1Q   Median       3Q      Max 
-1526.91  -298.80   -33.27   246.49  2277.60 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)  5914.21818   43.82511 134.950  < 2e-16 ***
parcial_5km     0.68045    0.11783   5.775 7.77e-09 ***
parcial_10km    0.74847    0.13279   5.637 1.75e-08 ***
parcial_15km   -2.57198    0.12101 -21.255  < 2e-16 ***
parcial_20km    2.62573    0.06379  41.159  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 416 on 36159 degrees of freedom
Multiple R-squared:  0.4465,    Adjusted R-squared:  0.4465 
F-statistic:  7293 on 4 and 36159 DF,  p-value: < 2.2e-16
par(mfrow = c(2, 2))   # 4 gráficos en una misma ventana
plot(modelo_parcial_hasta_mm)

par(mfrow = c(1, 1))
pred <- predict(modelo_parcial_hasta_mm)

plot(resultadosTokyo2025$tiempo_oficial, pred,
     xlab = "Tiempo real",
     ylab = "Tiempo predicho",
     main = "Predicción vs Real - modelo_parcial_hasta_mm")

abline(0, 1, col="red", lwd=2)  # línea ideal

library(ggplot2)

resultadosTokyo2025$pred <- predict(modelo_parcial_hasta_mm)

ggplot(resultadosTokyo2025, aes(x = pred, y = tiempo_oficial)) +
  geom_point() +
  geom_abline(intercept = 0, slope = 1, color = "red") +
  labs(title = "Predicciones vs valores reales - Modelo A",
       x = "Tiempo predicho",
       y = "Tiempo real") +
  theme_minimal()

5 Modelo de Regresión Logística para predecir si dará un bajón muy grande a partir del km 30

# PREPARACIÓN DE LOS DATOS

# Eliminar cartegoria No élite
df <- resultadosTokyo2025 %>%
  filter(categoria != "No élite") %>%
  mutate(categoria = droplevels(as.factor(categoria)))


unique(df$categoria)
[1] Élite                   Alto nivel              Muy entrenado          
[4] Moderadamente entrenado Principiante           
5 Levels: Élite Alto nivel Muy entrenado ... Principiante
# Encoding variable (categoria: Élite, Alto nivel, Muy entrenado, Moderadamente entrenado, Principiante)

df <- df %>%
  mutate(
    categoria_encoded = case_when(
      categoria == "Élite" ~ 4,
      categoria == "Alto nivel" ~ 3,
      categoria == "Muy entrenado" ~ 2,
      categoria == "Moderadamente entrenado" ~ 1,
      categoria == "Principiante" ~ 0
    )
  )

# Pasar a segundos los ritmos
# Calcular parciales reales (tiempos de cada segmento)
df <- df %>%
  mutate(
    seg_0_5 = parcial_5km,
    seg_5_10 = parcial_10km - parcial_5km,
    seg_10_15 = parcial_15km - parcial_10km,
    seg_15_20 = parcial_20km - parcial_15km,
    seg_20_25 = parcial_25km - parcial_20km,
    seg_25_30 = parcial_30km - parcial_25km,
    seg_30_35 = parcial_35km - parcial_30km,
    seg_35_40 = parcial_40km - parcial_35km,
    seg_40_meta = tiempo_oficial - parcial_40km
  )

# Normalizar por tiempo oficial (porcentaje de tiempo en cada segmento)
df <- df %>%
  mutate(
    rel_seg_0_5 = seg_0_5 / tiempo_oficial,
    rel_seg_5_10 = seg_5_10 / tiempo_oficial,
    rel_seg_10_15 = seg_10_15 / tiempo_oficial,
    rel_seg_15_20 = seg_15_20 / tiempo_oficial,
    rel_seg_20_25 = seg_20_25 / tiempo_oficial,
    rel_seg_25_30 = seg_25_30 / tiempo_oficial,
    rel_seg_30_35 = seg_30_35 / tiempo_oficial,
    rel_seg_35_40 = seg_35_40 / tiempo_oficial,
    rel_seg_40_meta = seg_40_meta / tiempo_oficial
  )


# Crear variable objetivo: bajón muy grande a partir del km 30
# Bonk = 1 si el corredor tiene un ritmo relativo mayor al 12% en alguno de los segmentos posteriores al km 30

df <- df %>%
  mutate(
    bonk = ifelse(
      (rel_seg_30_35 > 0.12) |
        (rel_seg_35_40 > 0.14) |
        (rel_seg_40_meta > 0.12),
      1,
      0
    )
  )

# Visualizar categorias y bonk
table(df$categoria, df$bonk)
                         
                              0     1
  Élite                      16    76
  Alto nivel                 82   151
  Muy entrenado             889  1080
  Moderadamente entrenado  2117  2438
  Principiante             7536 21741
# Seleccionar variables para el modelo
model_data <- df %>%
  select(
    bonk,
    categoria_encoded,
    rel_seg_0_5,
    rel_seg_5_10,
    rel_seg_10_15,
    rel_seg_15_20,
    rel_seg_20_25,
    rel_seg_25_30
  )
# Dividir en conjunto de entrenamiento y prueba
set.seed(123)

train_index <- createDataPartition(model_data$bonk, p = 0.8, list = FALSE)
train_data <- model_data[train_index, ]
test_data <- model_data[-train_index, ]

# MODELO DE REGRESIÓN LOGÍSTICA
logistic_model <- glm(
  bonk ~ .,
  data = train_data,
  family = binomial(link = "logit")
)

# Resumen del modelo
cat("=== RESUMEN MODELO DE REGRESIÓN LOGÍSTICA ===\n")
=== RESUMEN MODELO DE REGRESIÓN LOGÍSTICA ===
summary(logistic_model)

Call:
glm(formula = bonk ~ ., family = binomial(link = "logit"), data = train_data)

Coefficients:
                  Estimate Std. Error z value Pr(>|z|)    
(Intercept)        35.4800     0.6573   53.98   <2e-16 ***
categoria_encoded   1.0857     0.0307   35.37   <2e-16 ***
rel_seg_0_5       -55.4389     3.5831  -15.47   <2e-16 ***
rel_seg_5_10      -82.9637     4.8306  -17.17   <2e-16 ***
rel_seg_10_15     -80.4229     3.4707  -23.17   <2e-16 ***
rel_seg_15_20     -93.3691     3.3657  -27.74   <2e-16 ***
rel_seg_20_25     -68.5322     3.6295  -18.88   <2e-16 ***
rel_seg_25_30      55.3181     3.5000   15.80   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 35075  on 28900  degrees of freedom
Residual deviance: 23117  on 28893  degrees of freedom
AIC: 23133

Number of Fisher Scoring iterations: 6
# Predicciones en el conjunto de prueba
test_data$predicted_prob <- predict(logistic_model, newdata = test_data, type = "response")
test_data$predicted_class <- ifelse(test_data$predicted_prob > 0.5, 1, 0)

# Evaluación del modelo
confusion_matrix <- table(test_data$bonk, test_data$predicted_class)
cat("\n=== MATRIZ DE CONFUSIÓN ===\n")

=== MATRIZ DE CONFUSIÓN ===
print(confusion_matrix)
   
       0    1
  0 1336  770
  1  583 4536